﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BomberManServer.Utils
{
    public class ConcurrentDoubleBufferedLinkedList<T>
    {
        public class Handler
        {
            protected uint index;
            protected ConcurrentDoubleBufferedLinkedList<T> parent;
            protected LinkedList<T> list { get { return parent.lists[index]; } }
            protected Handler(ConcurrentDoubleBufferedLinkedList<T> parent, uint index)
            {
                this.parent = parent;
                this.index = index;
            }

            internal void swap()
            {
                index = (index + 1) % 2;
            }
        }

        public class Producer : Handler
        {
            internal Producer(ConcurrentDoubleBufferedLinkedList<T> parent)
                : base(parent, 0)
            {
            }

            public bool produce(T o)
            {
                // we can call this when swapping because we are the one swapping
                parent.producing = true;
                list.AddLast(o);
                parent.produced = true;

                if (!parent.consuming)
                    if (parent.consumed)
                        parent.swap();

                parent.producing = false;

                return true;
            }
        }

        public class Consumer : Handler
        {
            internal Consumer(ConcurrentDoubleBufferedLinkedList<T> parent)
                : base(parent, 1)
            {
            }

            public bool consume(out T o)
            {
                parent.consuming = true;
                // we are not the one swapping so we need to check if we are swapping
                if (parent.swapping)
                {
                    parent.consuming = false;
                    o = default(T);
                    return false;
                }
                // if list is empty
                if (list.Count == 0)
                {
                    parent.consuming = false;
                    o = default(T);
                    return false;
                }

                LinkedListNode<T> node = list.Last;
                list.RemoveLast();
                if (list.Count == 0) parent.consumed = true;
                parent.consuming = false;

                o = node.Value;
                return true;
            }
        }

        public Producer producer { get; private set; }
        public Consumer consumer { get; private set; }
        protected LinkedList<T>[] lists = new LinkedList<T>[2];
        protected bool producing = false, produced = false, consuming = false, consumed = true, swapping = false;

        public ConcurrentDoubleBufferedLinkedList()
        {
            lists[0] = new LinkedList<T>();
            lists[1] = new LinkedList<T>();
            this.producer = new Producer(this);
            this.consumer = new Consumer(this);
        }

        // we swap the buffers
        protected void swap()
        {
            swapping = true;
            this.consumer.swap();
            this.producer.swap();
            produced = false;
            consumed = false;
            swapping = false;
        }

    }
}
